#include <list.h>
#include <clk.h>
#include <errno.h>
#include <util.h>
#include <string.h>

static LIST_HEAD(clocks);

struct clk *clk_get(const char *name)
{
	struct clk *clkp;
	struct list_head *p;
	
	list_for_each(p, &clocks) {
		clkp = container_of(p, struct clk, n_clk_list);
		if (strcmp(clkp->name, name) == 0) {
			return clkp;
		}
	}
	return ERR_PTR(-ENODEV);
}

int clk_enable(struct clk *clk)
{
	int ret = 0;
	
    if (IS_ERR(clk) || clk == NULL)
        return -EINVAL;

	if (clk->parent)
		ret = clk_enable(clk->parent);

    if ((!ret) && ((clk->usage++) == 0) && clk->ops && clk->ops->enable)
        ret = clk->ops->enable(clk, 1);

    return ret;
}

int clk_disable(struct clk *clk)
{
	int ret = 0;
	
    if (IS_ERR(clk) || clk == NULL)
        return -EINVAL;

    if (((--clk->usage) == 0) && clk->ops && clk->ops->enable)
        ret = clk->ops->enable(clk, 0);
		
	if ((!ret) && (clk->parent))
		ret = clk_disable(clk->parent);
		
	return ret;
}

unsigned int clk_get_rate(struct clk *clk)
{
    if (IS_ERR(clk))
        return 0;

    if (clk->rate != 0)
        return clk->rate;

    if (clk->ops != NULL && clk->ops->get_rate != NULL)
        return clk->ops->get_rate(clk);

    if (clk->parent != NULL)
        return clk_get_rate(clk->parent);

    return clk->rate;
}

unsigned int clk_round_rate(struct clk *clk, unsigned int rate)
{
    if (!IS_ERR(clk) && clk->ops && clk->ops->round_rate)
        return clk->ops->round_rate(clk, rate);

    return rate;
}

int clk_set_rate(struct clk *clk, unsigned int rate)
{
    int ret;

    if (IS_ERR(clk))
        return -EINVAL;

    if (clk->ops == NULL || clk->ops->set_rate == NULL)
        return -EINVAL;

    ret = clk->ops->set_rate(clk, rate);

    return ret;
}

struct clk *clk_get_parent(struct clk *clk)
{
    return clk->parent;
}

int clk_set_parent(struct clk *clk, struct clk *parent)
{
    int ret = 0;

    if (IS_ERR(clk))
        return -EINVAL;

    if (clk->ops && clk->ops->set_parent)
        ret = clk->ops->set_parent(clk, parent);

    return ret;
}

struct clk *clk_get_by_device(struct device *dev)
{
	if (IS_ERR(dev) || dev == NULL)
		return ERR_PTR(-EINVAL);
	
	return dev->clk;
}

int s3c_register_clock(struct clk *clk)
{
	if (IS_ERR(clk) || clk == NULL)
		return -EINVAL;

	list_add_tail(&clk->n_clk_list, &clocks);
	return 0;
}

struct clk clk_xtal = {
	.name		= "xtal",
};

struct clk clk_mpll = {
	.name		= "mpll",
};

struct clk clk_upll = {
    .name		= "upll",
};

struct clk clk_f = {
    .name       = "fclk",
    .parent     = &clk_mpll,
};

struct clk clk_h = {
    .name       = "hclk",
};

struct clk clk_p = {
    .name       = "pclk",
};

void s3c_register_baseclocks(void)
{
	s3c_register_clock(&clk_xtal);
	s3c_register_clock(&clk_mpll);
	s3c_register_clock(&clk_upll);
	s3c_register_clock(&clk_f);
	s3c_register_clock(&clk_h);
	s3c_register_clock(&clk_p);
}

